package com.tqyan.service.transfer.xunfei;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.tqyan.service.service.ICallBack;
import com.tqyan.service.service.impl.SoundServiceImpl;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 语音听写流式 WebAPI 接口调用示例 接口文档（必看）：https://doc.xfyun.cn/rest_api/语音听写（流式版）.html
 * webapi 听写服务参考帖子（必看）：http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra=
 * 语音听写流式WebAPI 服务，热词使用方式：登陆开放平台https://www.xfyun.cn/后，找到控制台--我的应用---语音听写---个性化热词，上传热词
 * 注意：热词只能在识别的时候会增加热词的识别权重，需要注意的是增加相应词条的识别率，但并不是绝对的，具体效果以您测试为准。
 * 错误码链接：https://www.xfyun.cn/document/error-code （code返回错误码时必看）
 * 语音听写流式WebAPI 服务，方言或小语种试用方法：登陆开放平台https://www.xfyun.cn/后，在控制台--语音听写（流式）--方言/语种处添加
 * 添加后会显示该方言/语种的参数值
 * @author iflytek
 */
@Slf4j
@Data
public class SoundDictationWebSocketListener extends WebSocketListener {
    // 中英文，http url 不支持解析 ws/wss schema
    public static final String HOST_URL = "https://iat-api.xfyun.cn/v2/iat";
    // 小语种
    // private static final String HOST_URL = "https://iat-niche-api.xfyun.cn/v2/iat";

    public static final int STATUS_FIRST_FRAME = 0;
    public static final int STATUS_CONTINUE_FRAME = 1;
    public static final int STATUS_LAST_FRAME = 2;

    private Decoder decoder = new Decoder();
    private long beginTime = System.currentTimeMillis();

    private String appId;
    private String apiSecret;
    private String apiKey;
    private FileInputStream input;
    private ICallBack<com.tqyan.model.Result<String>> callBack;

    public SoundDictationWebSocketListener(String appId, String apiSecret, String apiKey
            , FileInputStream input, ICallBack<com.tqyan.model.Result<String>> callBack) {
        this.appId = appId;
        this.apiSecret = apiSecret;
        this.apiKey = apiKey;
        this.input = input;
        this.callBack = callBack;
    }

    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        super.onOpen(webSocket, response);
        this.send(webSocket);
    }

    public void send(WebSocket webSocket) {
        //连接成功，开始发送数据
        int frameSize = 1280; //每一帧音频的大小,建议每 40ms 发送 122B
        int intervel = 40;
        int status = 0;  // 音频的状态
        try {
            byte[] buffer = new byte[frameSize];
            // 发送音频
            while (status != STATUS_LAST_FRAME) {
                int len = input.read(buffer);
                if (len == -1) {
                    status = STATUS_LAST_FRAME;  //文件读完，改变status 为 2
                }
                switch (status) {
                    case STATUS_FIRST_FRAME:   // 第一帧音频status = 0
//                        log.info("send start");
                        JsonObject frame = new JsonObject();
                        JsonObject business = new JsonObject();  //第一帧必须发送
                        JsonObject common = new JsonObject();  //第一帧必须发送
                        JsonObject data = new JsonObject();  //每一帧都要发送
                        // 填充common
                        common.addProperty("app_id", appId);
                        //填充business
                        business.addProperty("language", "zh_cn");
//                        business.addProperty("language", "en_us");//英文
                        business.addProperty("domain", "iat");
                        business.addProperty("accent", "mandarin"); // 中文普通话
                        business.addProperty("dwa", "wpgs"); // 动态修正
                        //填充data
                        data.addProperty("status", STATUS_FIRST_FRAME);
                        data.addProperty("format", "audio/L16;rate=16000");
                        /**
                         * 音频数据格式
                         * raw：原生音频（支持单声道的pcm）
                         * speex：speex压缩后的音频（8k）
                         * speex-wb：speex压缩后的音频（16k）
                         * 请注意压缩前也必须是采样率16k或8k单声道的pcm。
                         * lame：mp3格式（仅中文普通话和英文支持，方言及小语种暂不支持）
                         */
                        data.addProperty("encoding", "lame");
                        data.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len)));
                        //填充frame
                        frame.add("common", common);
                        frame.add("business", business);
                        frame.add("data", data);
                        webSocket.send(frame.toString());
                        status = STATUS_CONTINUE_FRAME;  // 发送完第一帧改变status 为 1
                        break;
                    case STATUS_CONTINUE_FRAME:  //中间帧status = 1
                        JsonObject frame1 = new JsonObject();
                        JsonObject data1 = new JsonObject();
                        data1.addProperty("status", STATUS_CONTINUE_FRAME);
                        data1.addProperty("format", "audio/L16;rate=16000");
                        data1.addProperty("encoding", "raw");
                        data1.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len)));
                        frame1.add("data", data1);
                        webSocket.send(frame1.toString());
//                        log.info("send continue");
                        break;
                    case STATUS_LAST_FRAME:    // 最后一帧音频status = 2 ，标志音频发送结束
                        JsonObject frame2 = new JsonObject();
                        JsonObject data2 = new JsonObject();
                        data2.addProperty("status", STATUS_LAST_FRAME);
                        data2.addProperty("audio", "");
                        data2.addProperty("format", "audio/L16;rate=16000");
                        data2.addProperty("encoding", "raw");
                        frame2.add("data", data2);
                        webSocket.send(frame2.toString());
//                        log.info("send last");
                        break;
                }
                Thread.sleep(intervel); //模拟音频采样延时
            }
            input.close();
            log.info("[语音识别] 数据发送完成");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void onMessage(WebSocket webSocket, String text) {
        super.onMessage(webSocket, text);
//        log.info("onMessage: " + text);
        ResponseData resp = new Gson().fromJson(text, ResponseData.class);
        if (resp != null) {
            if (resp.getCode() != 0) {
                log.info( "code=>" + resp.getCode() + " error=>" + resp.getMessage() + " sid=" + resp.getSid());
//                log.info( "错误码查询链接：https://www.xfyun.cn/document/error-code");
                webSocket.close(1000, "");
                // 回调处理
                com.tqyan.model.Result<String> result = com.tqyan.model.Result.fail(resp.getMessage());
                result.setData(JSONObject.toJSONString(resp));
                callBack.handle(result);
                return;
            }
            if (resp.getData() != null) {
                if (resp.getData().getResult() != null) {
                    Text te = resp.getData().getResult().getText();
                    try {
                        decoder.decode(te);
//                        log.info("中间识别结果 ==》" + decoder.toString());
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
                if (resp.getData().getStatus() == 2) {
                    // resp.data.status ==2 说明数据全部返回完毕，可以关闭连接，释放资源
                    log.info("耗时: {}ms", System.currentTimeMillis() - beginTime);
                    String result = decoder.toString();
                    decoder.discard();
                    log.info("最终识别结果: {}", result);
                    webSocket.close(1000, "");
                    // 回调处理
                    callBack.handle(com.tqyan.model.Result.success(result));
                } else {
                    // 根据返回的数据处理
//                    log.info("else: " + text);
                }
            }
        }
    }

    @Override
    public void onClosing(WebSocket webSocket, int code, String reason) {
        super.onClosing(webSocket, code, reason);
//        log.info("onClosing, code={}, reason={}", code, reason);
    }

    @Override
    public void onClosed(WebSocket webSocket, int code, String reason) {
        super.onClosed(webSocket, code, reason);
//        log.info("onClosed, code={}, reason={}", code, reason);
    }

    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        super.onFailure(webSocket, t, response);
//        log.info("onFailure: " + response);
        try {
            if (null != response) {
                int code = response.code();
                log.info("onFailure code:" + code);
                log.info("onFailure body:" + response.body().string());
//                if (101 != code) {
//                    log.info("connection failed");
//                    System.exit(0);
//                }
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    public String getAuthUrl(String hostUrl) throws Exception {
        String algorithm = "hmacsha256";
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        Charset charset = StandardCharsets.UTF_8;
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(apiSecret.getBytes(charset), algorithm));
        URL url = new URL(hostUrl);
        String builder = "host: " + url.getHost()
                + "\ndate: " + date
                + "\nGET " + url.getPath()
                + " HTTP/1.1";
        byte[] hexDigits = mac.doFinal(builder.getBytes(charset));
        String sha = Base64.getEncoder().encodeToString(hexDigits);
        String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\""
                , apiKey, "hmac-sha256", "host date request-line", sha);
        HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath()))
                .newBuilder()
                .addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset)))
                .addQueryParameter("date", date)
                .addQueryParameter("host", url.getHost())
                .build();
        return httpUrl.toString();
    }

    public class ResponseData {
        private int code;
        private String message;
        private String sid;
        private Data data;
        public int getCode() {
            return code;
        }
        public String getMessage() {
            return this.message;
        }
        public String getSid() {
            return sid;
        }
        public Data getData() {
            return data;
        }
    }

    public class Data {
        private int status;
        private Result result;
        public int getStatus() {
            return status;
        }
        public Result getResult() {
            return result;
        }
    }

    public class Result {
        int bg;
        int ed;
        String pgs;
        int[] rg;
        int sn;
        Ws[] ws;
        boolean ls;
        JsonObject vad;

        public Text getText() {
            Text text = new Text();
            StringBuilder sb = new StringBuilder();
            for (Ws ws : this.ws) {
                sb.append(ws.cw[0].w);
            }
            text.sn = this.sn;
            text.text = sb.toString();
            text.sn = this.sn;
            text.rg = this.rg;
            text.pgs = this.pgs;
            text.bg = this.bg;
            text.ed = this.ed;
            text.ls = this.ls;
            text.vad = this.vad==null ? null : this.vad;
            return text;
        }
    }

    public class Ws {
        Cw[] cw;
        int bg;
        int ed;
    }

    public class Cw {
        int sc;
        String w;
    }

    public class Text {
        int sn;
        int bg;
        int ed;
        String text;
        String pgs;
        int[] rg;
        boolean deleted;
        boolean ls;
        JsonObject vad;

        @Override
        public String toString() {
            return "Text{" +
                    "bg=" + bg +
                    ", ed=" + ed +
                    ", ls=" + ls +
                    ", sn=" + sn +
                    ", text='" + text + '\'' +
                    ", pgs=" + pgs +
                    ", rg=" + Arrays.toString(rg) +
                    ", deleted=" + deleted +
                    ", vad=" + (vad==null ? "null" : vad.getAsJsonArray("ws").toString()) +
                    '}';
        }
    }

    //解析返回数据，仅供参考
    public class Decoder {
        private Text[] texts;
        private int defc = 10;

        public Decoder() {
            this.texts = new Text[this.defc];
        }

        public synchronized void decode(Text text) {
            if (text.sn >= this.defc) {
                this.resize();
            }
            if ("rpl".equals(text.pgs)) {
                for (int i = text.rg[0]; i <= text.rg[1]; i++) {
                    this.texts[i].deleted = true;
                }
            }
            this.texts[text.sn] = text;
        }

        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (Text t : this.texts) {
                if (t != null && !t.deleted) {
                    sb.append(t.text);
                }
            }
            return sb.toString();
        }

        public void resize() {
            int oc = this.defc;
            this.defc <<= 1;
            Text[] old = this.texts;
            this.texts = new Text[this.defc];
            if (oc >= 0) System.arraycopy(old, 0, this.texts, 0, oc);
        }

        public void discard() {
            Arrays.fill(this.texts, null);
        }
    }

    public static void main(String[] args) throws Exception {
        String file = "/Users/chenchen/Desktop/3，工作/X，AI/语音.mp3";
        SoundDictationWebSocketListener xunfei = new SoundDictationWebSocketListener(
                SoundServiceImpl.APP_ID, SoundServiceImpl.API_SECRET, SoundServiceImpl.API_KEY
                , new FileInputStream(file)
                , result -> {
                    log.info("result={}", result.getData());
                }
        );
        String authUrl = xunfei.getAuthUrl(HOST_URL);
        String url = authUrl.replace("http://", "ws://").replace("https://", "wss://");
        Request request = new Request.Builder().url(url).build();
        OkHttpClient client = new OkHttpClient.Builder().build();
        client.newWebSocket(request, xunfei);
        log.info("========== 语音识别 End ==========");
    }

}


